﻿using SuperX.Common;
using SuperX.Common.Abstract;
using SuperX.Common.BasicService;
using SuperX.Common.Collection;
using SuperX.Common.Log;
using SuperX.Common.Object;
using SuperX.DataService.Abstract;
using Newtonsoft.Json;
using NPOI.SS.Formula.Functions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading;
using System.Timers;

namespace SuperX.DataService
{
    /// <summary>
    /// 批量读取数据
    /// 1.对数据地址排序
    /// 2.对数据分块 –UpdatePDUArea函数
    /// 3.轮询刷新数据-Poll函数
    /// 4.比较数据，抽取变化部分_changedList
    /// 5.推送变化数据，DataChange事件
    /// </summary>
    public class PLCGroup : BaseDeviceGroup, IGroup
    {
        private Thread _thread;

        private bool IsRunning;

        /// <summary>
        /// 是否激活
        /// </summary>
        public override bool IsActive
        {
            get
            {
                return _isActive;
            }
            set
            {
                _isActive = value;
                if (value)
                {
                    if (_updateRate <= 0) _updateRate = 100;
                    IsRunning = true;
                    _thread = new Thread(Loop) { IsBackground = true };
                    _thread.Start();
                }
                else
                {
                    IsRunning = false;
                    _thread?.Abort();
                }
            }
        }

        protected IPLCDriver _plcReader;

        [JsonIgnore]
        public IDriver IDriverParent
        {
            get
            {
                return _plcReader;
            }
        }

        protected List<int> _changedList;

        public List<int> ChangedList
        {
            get
            {
                return _changedList;
            }
        }

        [JsonIgnore]
        public override IBasicObject this[string name]
        {
            get => IOTagGroup[name];
        }

        protected List<PDUArea> _rangeList = new List<PDUArea>();

        protected ICache _cacheReader;

        protected PLCGroup()
        {
        }

        public PLCGroup(string name, int updateRate, bool active, IPLCDriver plcReader)
        {
            _items = new List<IIOTag>();
            this.Name = name;
            this._updateRate = updateRate;
            this._isActive = active;
            this._plcReader = plcReader;
            this._server = _plcReader.DriverParent;
            this._changedList = new List<int>();
            this._cacheReader = new ByteCacheReader();
        }

        /// <summary>
        /// 配置的变量转换成IOTag变量并存到缓存中
        /// </summary>
        /// <param name="items"></param>
        /// <returns></returns>
        public bool AddItems(IList<TagConfig> items)
        {
            int count = items.Count;
            if (_items == null) _items = new List<IIOTag>(count);
            lock (_server.SyncRoot)
            {
                for (int i = 0; i < count; i++)
                {
                    IIOTag dataItem = null;
                    var meta = items[i];
                    DeviceAddress addr = _plcReader.GetDeviceAddress(meta);
                    if (string.IsNullOrEmpty(addr.Addr) && meta.TagType == TagType.Normal) continue;
                    if (addr.DataSize == 0) addr.DataSize = meta.Size;
                    if (addr.VarType != DataType.BOOL) addr.Bit = 0;
                    switch (meta.DataType)
                    {
                        case DataType.BOOL:
                            dataItem = new BoolTag(addr, this);
                            break;

                        case DataType.BYTE:
                            dataItem = new ByteTag(addr, this);
                            break;

                        case DataType.WORD:
                            dataItem = new UShortTag(addr, this);
                            break;

                        case DataType.SHORT:
                            dataItem = new ShortTag(addr, this);
                            break;

                        case DataType.DWORD:
                            dataItem = new UIntTag(addr, this);
                            break;

                        case DataType.INT:
                            dataItem = new IntTag(addr, this);
                            break;

                        case DataType.FLOAT:
                            dataItem = new FloatTag(addr, this);
                            break;

                        case DataType.STR:
                            dataItem = new StringTag(addr, this);
                            break;
                    }
                    if (dataItem != null)
                    {
                        dataItem.Name = meta.Name;
                        dataItem.Interval = meta.Interval;
                        dataItem.Archive = meta.Archive;
                        dataItem.ReadWriteMode = meta.ReadWriteMode;
                        dataItem.Script = meta.Script;
                        dataItem.Maximum = meta.Maximum;
                        dataItem.Minimum = meta.Minimum;
                        dataItem.TagType = meta.TagType;
                        dataItem.Regester = meta.Regester;
                        dataItem.No = meta.No;
                        dataItem.ScaleValue = meta.ScaleValue;
                        dataItem.ConverterType = meta.ConverterType;
                        dataItem.Unit = meta.Unit;
                        dataItem.Description = meta.Description;
                        dataItem.TimeStamp = DateTime.Now;
                        //dataItem.Active = meta.Active;
                        IOTagGroup.Add(dataItem);
                        _server.AddItemIndex(meta.Name, dataItem);
                    }
                }
            }
            _items = IOTagGroup.Values;
            _items.TrimExcess();
            UpdatePDUArea();
            return true;
        }

        private void AddressSort(List<IIOTag> items)
        {
            _items = _items.
                Where(a => a.TagType == TagType.Normal).
                OrderBy(a => a.Address).
                ThenBy(a => a.Address.VarType).
                ToList();
        }

        /// <summary>
        /// 根据PDU大小划分块
        /// </summary>
        protected void UpdatePDUArea()
        {
            AddressSort(_items);
            int count = _items.Count;
            if (count > 0)
            {
                DeviceAddress _start = _items[0].Address;
                _start.Bit = 0;
                int bitCount = _cacheReader.ByteCount;
                if (count > 1)
                {
                    int cacheLength = 0;//缓冲区的大小
                    int cacheIndexStart = 0;
                    int startIndex = 0;
                    DeviceAddress segmentEnd = DeviceAddress.Empty;
                    DeviceAddress tagAddress = DeviceAddress.Empty;
                    DeviceAddress segmentStart = _start;
                    for (int j = 1, i = 1; i < count; i++, j++)
                    {
                        tagAddress = _items[i].Address;//当前变量地址
                        int offset1 = _cacheReader.GetOffset(tagAddress, segmentStart);
                        if (offset1 > (_plcReader.PDU - tagAddress.DataSize) / bitCount)
                        {
                            segmentEnd = _items[i - 1].Address;
                            int len = _cacheReader.GetOffset(segmentEnd, segmentStart);
                            len += segmentEnd.DataSize <= bitCount ? 1 : segmentEnd.DataSize / bitCount;
                            tagAddress.CacheIndex = (ushort)(cacheIndexStart + len);
                            _items[i].Address = tagAddress;
                            var startBit = _items.Where(a => a.Address.Addr == segmentStart.Addr).FirstOrDefault().Address.Bit;
                            len = ConvertLength(len, segmentEnd);
                            _rangeList.Add(new PDUArea(segmentStart, len, startIndex, j, startBit));
                            startIndex += j; j = 0;
                            cacheLength += len;//更新缓存长度
                            cacheIndexStart = cacheLength;
                            segmentStart = tagAddress;//更新数据片段的起始地址
                        }
                        else
                        {
                            tagAddress.CacheIndex = (ushort)(cacheIndexStart + offset1);
                            _items[i].Address = tagAddress;
                            var tag = _items[i];
                        }
                        if (i == count - 1)
                        {
                            segmentEnd = _items[i].Address;
                            int segmentLength = _cacheReader.GetOffset(segmentEnd, segmentStart);
                            if (segmentLength > (_plcReader.PDU - segmentEnd.DataSize) / bitCount)
                            {
                                segmentEnd = _items[i - 1].Address;
                                segmentLength = segmentEnd.DataSize <= bitCount ? 1 : segmentEnd.DataSize / bitCount;
                            }
                            tagAddress.CacheIndex = (ushort)(cacheIndexStart + segmentLength);
                            _items[i].Address = tagAddress;
                            segmentLength += segmentEnd.DataSize <= bitCount ? 1 : segmentEnd.DataSize / bitCount;
                            var startBit = _items.Where(a => a.Address.Addr == segmentStart.Addr).FirstOrDefault().Address.Bit;
                            segmentLength = ConvertLength(segmentLength, segmentEnd);
                            _rangeList.Add(new PDUArea(segmentStart, segmentLength, startIndex, j + 1, startBit));
                            cacheLength += segmentLength;
                        }
                    }
                    _cacheReader.Size = cacheLength;
                }
                else
                {
                    var size = _start.DataSize <= bitCount ? 1 : _start.DataSize / bitCount;
                    var start = _items.Where(a => a.Address.Addr == _start.Addr).FirstOrDefault();
                    var startBit = start.Address.Bit;
                    size = ConvertLength(size, start.Address);
                    _rangeList.Add(new PDUArea(_start, size, 0, 1, startBit));
                    _cacheReader.Size = size;//改变Cache的Size属性值将创建Cache的内存区域
                }
            }
        }

        /// <summary>
        /// 最后一个地址的数据类型，数字量4个位组成一个byte
        /// </summary>
        /// <param name="length"></param>
        /// <param name="end"></param>
        /// <returns></returns>
        protected virtual int ConvertLength(int length, DeviceAddress end)
        {
            if (end.GatherType == GatherType.Digital)
                return length + 32;
            else
            {
                switch (end.VarType)
                {
                    case DataType.BYTE:
                        return length + 1;

                    case DataType.SHORT:
                    case DataType.WORD:
                        return length + 1;

                    case DataType.DWORD:
                    case DataType.INT:
                    case DataType.FLOAT:
                        return length + 4;

                    default:
                        return length;
                }
            }
        }

        private object sync = new object();

        /// <summary>
        /// 轮询线程，检查有变化的数据，放入changedList中
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected void timer_Timer(object sender, EventArgs e)
        {
            if (_isActive)
            {
                lock (sync)
                {
                    _changedList.Clear();
                    Poll();
                    if (_changedList.Count > 0)
                        Update();
                }
            }
            else
                return;
        }

        private void Loop()
        {
            while (IsActive)
            {
                try
                {
                    _changedList.Clear();
                    Poll();
                    UpdateExclusiveTag();
                    if (_changedList.Count > 0)
                        Update();
                }
                catch (Exception ex)
                {
                    Logger.Warn($"分组名：{this.FullName} group Loop faild:{ex.Message}");
                }
                finally
                {
                    Thread.Sleep(UpdateRate);
                }
            }
        }

        /// <summary>
        /// 轮询变量，变量有变化放到_changedList中
        /// </summary>
        protected virtual void Poll()
        {
            if (_plcReader.IsClosed) return;
            byte[] cache = (byte[])_cacheReader.Cache;
            int offset = 0;
            foreach (PDUArea area in _rangeList)
            {
                byte[] rcvBytes = _plcReader.ReadBytes(area.Start, (ushort)area.Len);//从PLC读取数据
                if (rcvBytes == null)
                {
                    //_plcReader.Connect();
                    continue;
                }
                else
                {
                    int index = area.StartIndex;//index指向_items中的Tag元数据
                    int count = index + area.Count;
                    while (index < count)
                    {
                        DeviceAddress addr = _items[index].Address;
                        int iByte = addr.CacheIndex;
                        int iByte1 = iByte - offset;
                        if (addr.VarType == DataType.BOOL)
                        {
                            int tmp = rcvBytes[iByte1] ^ cache[iByte];
                            DeviceAddress next = addr;
                            if (tmp != 0)
                            {
                                while (addr.Start == next.Start)
                                {
                                    if ((tmp & (1 << next.Bit)) > 0) _changedList.Add(index);
                                    if (++index < count)
                                        next = _items[index].Address;
                                    else
                                        break;
                                }
                            }
                            else
                            {
                                while (addr.Start == next.Start && ++index < count)
                                {
                                    next = _items[index].Address;
                                }
                            }
                        }
                        else
                        {
                            ushort size = addr.DataSize;
                            for (int i = 0; i < size; i++)
                            {
                                if (iByte1 + i < rcvBytes.Length && rcvBytes[iByte1 + i] != cache[iByte + i])
                                {
                                    _changedList.Add(index);
                                    break;
                                }
                            }
                            index++;
                        }
                    }
                    for (int j = 0; j < rcvBytes.Length; j++)
                        cache[j + offset] = rcvBytes[j];//将PLC读取的数据写入到CacheReader中
                }
                offset += rcvBytes.Length;
            }
        }

        protected virtual void UpdateExclusiveTag()
        {
            var tags = this.IOTagGroup.Values.Where(a => a.TagType == TagType.Exclusive || a.TagType == TagType.System).ToList();
            foreach (var item in tags)
            {
                if (!string.IsNullOrEmpty(item.Script))
                {
                    item.ExecuteExpression();
                }
            }
        }

        public override void Dispose()
        {
            IsRunning = false;
            _thread?.Abort();
            base.Dispose();
        }

        public ItemData<int> ReadInt32(DeviceAddress address, DataSource source = DataSource.Cache)
        {
            return source == DataSource.Cache ? _cacheReader.ReadInt32(address) : _plcReader.ReadInt32(address);
        }

        public ItemData<uint> ReadUInt32(DeviceAddress address, DataSource source = DataSource.Cache)
        {
            return source == DataSource.Cache ? _cacheReader.ReadUInt32(address) : _plcReader.ReadUInt32(address);
        }

        public ItemData<ushort> ReadUInt16(DeviceAddress address, DataSource source = DataSource.Cache)
        {
            return source == DataSource.Cache ? _cacheReader.ReadUInt16(address) : _plcReader.ReadUInt16(address);
        }

        public ItemData<short> ReadInt16(DeviceAddress address, DataSource source = DataSource.Cache)
        {
            return source == DataSource.Cache ? _cacheReader.ReadInt16(address) : _plcReader.ReadInt16(address);
        }

        public ItemData<byte> ReadByte(DeviceAddress address, DataSource source = DataSource.Cache)
        {
            return source == DataSource.Cache ? _cacheReader.ReadByte(address) : _plcReader.ReadByte(address);
        }

        public ItemData<float> ReadFloat(DeviceAddress address, DataSource source = DataSource.Cache)
        {
            return source == DataSource.Cache ? _cacheReader.ReadFloat(address) : _plcReader.ReadFloat(address);
        }

        public ItemData<bool> ReadBool(DeviceAddress address, DataSource source = DataSource.Cache)
        {
            return source == DataSource.Cache ? _cacheReader.ReadBit(address) : _plcReader.ReadBit(address);
        }

        public ItemData<string> ReadString(DeviceAddress address, DataSource source = DataSource.Cache)
        {
            ushort siz = address.DataSize;
            return source == DataSource.Cache ? _cacheReader.ReadString(address, siz) :
                _plcReader.ReadString(address, siz);
        }

        public int WriteInt32(DeviceAddress address, int value)
        {
            int rs = _plcReader.WriteInt32(address, value);
            return rs;
        }

        public int WriteUInt32(DeviceAddress address, uint value)
        {
            int rs = _plcReader.WriteUInt32(address, value);

            return rs;
        }

        public int WriteUInt16(DeviceAddress address, ushort value)
        {
            int rs = _plcReader.WriteUInt16(address, value);
            return rs;
        }

        public int WriteInt16(DeviceAddress address, short value)
        {
            int rs = _plcReader.WriteInt16(address, value);
            return rs;
        }

        public int WriteFloat(DeviceAddress address, float value)
        {
            int rs = _plcReader.WriteFloat(address, value);
            return rs;
        }

        public int WriteString(DeviceAddress address, string value)
        {
            int rs = _plcReader.WriteString(address, value);
            return rs;
        }

        public int WriteBit(DeviceAddress address, bool value)
        {
            int rs = _plcReader.WriteBit(address, value);
            return rs;
        }

        public int WriteBits(DeviceAddress address, byte value)
        {
            int rs = _plcReader.WriteBits(address, value);
            return rs;
        }

        public ItemData<dynamic> ReadDynamic(DeviceAddress address, DataSource source = DataSource.Cache)
        {
            throw new NotImplementedException();
        }

        public int WriteDouble(DeviceAddress address, double value)
        {
            throw new NotImplementedException();
        }

        public int WriteLong(DeviceAddress address, long value)
        {
            throw new NotImplementedException();
        }

        public event DataChangeEventHandler DataChange;
    }
}